package com.googlecode.restitory.gae.http;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.appengine.api.urlfetch.HTTPRequest;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
import com.googlecode.restitory.gae.GAERequestUtil;
import com.googlecode.restitory.gae.Request;
import com.googlecode.restitory.gae.RequestJsoner;
import com.googlecode.restitory.gae.Response;
import com.googlecode.restitory.gae.TextContent;
import com.googlecode.restitory.gae.Type;
import com.googlecode.restitory.gae.hook.AuthHook;
import com.googlecode.restitory.gae.hook.Chain;
import com.googlecode.restitory.gae.hook.DefaultRequestService;
import com.googlecode.restitory.gae.hook.Hook;

public class UrlFetcherHook implements Hook {

	private static final Logger LOG = LoggerFactory.getLogger(AuthHook.class);

	public static final String CALLBACK_PARAM = "_urlFetcherCallback";

	@Override
	public Response execute(Request request, Chain chain) {
		String path = request.getPath();
		if (path.equals("/_/urlfetcher")) {
			return urlFetcher(request);
		}
		return chain.execute(request);
	}

	private Response urlFetcher(Request request) {
		LOG.info("url fetch: " + request + " " + request.getContent());
		TextContent content = (TextContent) request.getContent();
		char[] chars = content.getValue();
		List<Request> reqs = RequestJsoner.parseRequestListJson(chars);

		List<Future<Response>> resps = invoke(reqs);
		consume(request, resps);
		return new Response(request).setCode(200);
	}

	private void consume(Request request, List<Future<Response>> resps) {
		try {
			int i = 0;
			while (!resps.isEmpty()) {
				try {
					if (i >= resps.size()) {
						i = 0;
					}
					Future<Response> future = resps.get(i);
					Response resp = future.get(1000, TimeUnit.MILLISECONDS);
					resps.remove(i);
					resolve(resp);
				} catch (TimeoutException e) {
				}
				i++;
			}
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		} catch (ExecutionException e) {
			throw new RuntimeException(e);
		}
	}

	private void resolve(Response resp) {
		LOG.info("url fetch resolve: " + resp);
		Request req = resp.getRequest();
		String callback = req.getParams().getFirst(CALLBACK_PARAM);
		String[] split = callback.split(":");
		String method = split[0];
		String target = split[1];

		String json = RequestJsoner.formatJson(resp);
		Request c = new Request(Type.valueOf(method), target).setContentType("application/json").setContent(json);
		Chain chain = DefaultRequestService.createChain();
		chain.removeHook(AuthHook.class);
		chain.execute(c);
	}

	private List<Future<Response>> invoke(List<Request> reqs) {
		URLFetchService service = getURLFetchService();

		List<Future<Response>> ret = new ArrayList<Future<Response>>();
		for (Request request : reqs) {
			Future<Response> resp = invoke(service, request);
			ret.add(resp);
		}

		return ret;
	}

	private Future<Response> invoke(URLFetchService service, Request request) {
		Request req = request.copy();
		req.getParams().clear(CALLBACK_PARAM);
		HTTPRequest httpReq = GAERequestUtil.requestToHttp(req);
		Future<HTTPResponse> ret = service.fetchAsync(httpReq);
		return new FutureHttpReponse(request, ret);
	}

	private URLFetchService getURLFetchService() {
		return URLFetchServiceFactory.getURLFetchService();
	}

}
